1   // Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
2   //
3   // Licensed under the Apache License, Version 2.0 (the "License");
4   // you may not use this file except in compliance with the License.
5   // You may obtain a copy of the License at
6   //
7   // http://www.apache.org/licenses/LICENSE-2.0
8   //
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
14  
15  package org.apache.tapestry5.internal.bindings;
16  
17  import org.apache.tapestry5.Binding;
18  import org.apache.tapestry5.ComponentResources;
19  import org.apache.tapestry5.annotations.BeforeRenderBody;
20  import org.apache.tapestry5.beaneditor.Validate;
21  import org.apache.tapestry5.internal.test.InternalBaseTestCase;
22  import org.apache.tapestry5.internal.util.IntegerRange;
23  import org.apache.tapestry5.ioc.Location;
24  import org.apache.tapestry5.ioc.internal.util.InternalUtils;
25  import org.apache.tapestry5.ioc.internal.util.TapestryException;
26  import org.apache.tapestry5.runtime.Component;
27  import org.apache.tapestry5.services.BindingFactory;
28  import org.testng.annotations.AfterClass;
29  import org.testng.annotations.BeforeClass;
30  import org.testng.annotations.DataProvider;
31  import org.testng.annotations.Test;
32  
33  public class PropBindingFactoryTest extends InternalBaseTestCase
34  {
35      private BindingFactory factory;
36  
37      @BeforeClass
38      public void setup_factory()
39      {
40          factory = getService("PropBindingFactory", BindingFactory.class);
41      }
42  
43      @AfterClass
44      public void cleanup_factory()
45      {
46          factory = null;
47      }
48  
49      private ComponentResources newComponentResources(Component component)
50      {
51          ComponentResources resources = mockComponentResources();
52          train_getComponent(resources, component);
53  
54          train_getCompleteId(resources, "foo.Bar:baz");
55  
56          return resources;
57      }
58  
59      @Test
60      public void object_property()
61      {
62          TargetBean bean = new TargetBean();
63          ComponentResources resources = newComponentResources(bean);
64          Location l = mockLocation();
65  
66          replay();
67  
68          Binding binding = factory.newBinding("test binding", resources, null, "objectValue", l);
69  
70          assertSame(binding.getBindingType(), String.class);
71  
72          bean.setObjectValue("first");
73  
74          assertEquals(binding.get(), "first");
75  
76          binding.set("second");
77  
78          assertEquals(bean.getObjectValue(), "second");
79          assertEquals(InternalUtils.locationOf(binding), l);
80  
81          assertEquals(binding.toString(), "PropBinding[test binding foo.Bar:baz(objectValue)]");
82  
83          verify();
84      }
85  
86      @Test
87      public void annotation_from_read_only_property()
88      {
89          TargetBean bean = new TargetBean();
90          ComponentResources resources = newComponentResources(bean);
91          Location l = mockLocation();
92  
93          replay();
94  
95          Binding binding = factory.newBinding("test binding", resources, null, "readOnly", l);
96  
97          assertEquals(binding.getAnnotation(Validate.class).value(), "readonly");
98  
99          verify();
100     }
101 
102     @Test
103     public void annotation_from_write_only_property()
104     {
105         TargetBean bean = new TargetBean();
106         ComponentResources resources = newComponentResources(bean);
107         Location l = mockLocation();
108 
109         replay();
110 
111         Binding binding = factory.newBinding("test binding", resources, null, "writeOnly", l);
112 
113         assertEquals(binding.getAnnotation(Validate.class).value(), "writeonly");
114 
115         verify();
116     }
117 
118     @Test
119     public void annotation_does_not_exist()
120     {
121         TargetBean bean = new TargetBean();
122         ComponentResources resources = newComponentResources(bean);
123         Location l = mockLocation();
124 
125         replay();
126 
127         Binding binding = factory.newBinding("test binding", resources, null, "intValue", l);
128 
129         assertNull(binding.getAnnotation(Validate.class));
130 
131         verify();
132     }
133 
134     @Test
135     public void annotation_on_named_method()
136     {
137         TargetBean bean = new TargetBean();
138         ComponentResources resources = newComponentResources(bean);
139         Location l = mockLocation();
140 
141         replay();
142 
143         Binding binding = factory.newBinding("test binding", resources, null, "stringHolderMethod()", l);
144 
145         assertNotNull(binding.getAnnotation(BeforeRenderBody.class));
146 
147         verify();
148     }
149 
150     @Test
151     public void annnotation_on_read_method_takes_precedence_over_write_method()
152     {
153         TargetBean bean = new TargetBean();
154         ComponentResources resources = newComponentResources(bean);
155         Location l = mockLocation();
156 
157         replay();
158 
159         Binding binding = factory.newBinding("test binding", resources, null, "objectValue", l);
160 
161         assertEquals(binding.getAnnotation(Validate.class).value(), "getObjectValue");
162 
163         verify();
164     }
165 
166     @Test
167     public void property_path()
168     {
169         TargetBean bean = new TargetBean();
170         ComponentResources resources = newComponentResources(bean);
171         Location l = mockLocation();
172 
173         replay();
174 
175         Binding binding = factory.newBinding("test binding", resources, null, "stringHolder.value", l);
176 
177         assertSame(binding.getBindingType(), String.class);
178 
179         bean.getStringHolder().setValue("first");
180 
181         assertEquals(binding.get(), "first");
182 
183         binding.set("second");
184 
185         assertEquals(bean.getStringHolder().getValue(), "second");
186 
187         assertEquals(binding.toString(), "PropBinding[test binding foo.Bar:baz(stringHolder.value)]");
188 
189         verify();
190     }
191 
192     /**
193      * The "preamble" are the non-terminal property or method names.
194      */
195     @Test
196     public void property_path_with_explicit_method_in_preamble()
197     {
198         TargetBean bean = new TargetBean();
199         ComponentResources resources = newComponentResources(bean);
200         Location l = mockLocation();
201 
202         replay();
203 
204         Binding binding = factory.newBinding("test binding", resources, null, "stringHolderMethod().value", l);
205 
206         assertSame(binding.getBindingType(), String.class);
207 
208         bean.getStringHolder().setValue("first");
209 
210         assertEquals(binding.get(), "first");
211 
212         assertEquals(binding.toString(), "PropBinding[test binding foo.Bar:baz(stringHolderMethod().value)]");
213 
214         verify();
215     }
216 
217     @Test
218     public void method_call_as_terminal()
219     {
220         TargetBean bean = new TargetBean();
221         ComponentResources resources = newComponentResources(bean);
222         Location l = mockLocation();
223 
224         replay();
225 
226         Binding binding = factory.newBinding("test binding", resources, null, "stringHolderMethod().stringValue()", l);
227 
228         assertSame(binding.getBindingType(), String.class);
229 
230         bean.getStringHolder().setValue("first");
231 
232         assertEquals(binding.get(), "first");
233 
234         try
235         {
236             binding.set("read-only");
237             unreachable();
238         }
239         catch (TapestryException ex)
240         {
241             assertEquals(
242                     ex.getMessage(),
243                     "Expression 'stringHolderMethod().stringValue()' for class org.apache.tapestry5.internal.bindings.TargetBean is read-only.");
244             assertSame(ex.getLocation(), l);
245         }
246 
247         verify();
248     }
249 
250     @Test
251     public void method_not_found_in_preamble()
252     {
253         TargetBean bean = new TargetBean();
254         ComponentResources resources = mockComponentResources();
255         Location l = mockLocation();
256 
257         train_getComponent(resources, bean);
258 
259         replay();
260 
261         try
262         {
263             factory.newBinding("test binding", resources, null, "isThatRealBlood().value", l);
264             unreachable();
265         }
266         catch (RuntimeException ex)
267         {
268             assertMessageContains(ex,
269                     "Class org.apache.tapestry5.internal.bindings.TargetBean does not contain a public method named 'isThatRealBlood()'");
270         }
271 
272         verify();
273     }
274 
275     @Test
276     public void method_not_found_in_terminal()
277     {
278         TargetBean bean = new TargetBean();
279         ComponentResources resources = mockComponentResources();
280         Location l = mockLocation();
281 
282         train_getComponent(resources, bean);
283 
284         replay();
285 
286         try
287         {
288             factory.newBinding("test binding", resources, null, "stringHolder.isThatRealBlood()", l);
289             unreachable();
290         }
291         catch (RuntimeException ex)
292         {
293             assertMessageContains(ex, "StringHolder", "does not contain a public method", "isThatRealBlood()");
294         }
295 
296         verify();
297     }
298 
299     @Test
300     public void void_method_in_preamble()
301     {
302         TargetBean bean = new TargetBean();
303         ComponentResources resources = mockComponentResources();
304         Location l = mockLocation();
305 
306         train_getComponent(resources, bean);
307 
308         replay();
309 
310         try
311         {
312             factory.newBinding("test binding", resources, null, "voidMethod().value", l);
313             unreachable();
314         }
315         catch (RuntimeException ex)
316         {
317             assertMessageContains(ex,
318                     "Method org.apache.tapestry5.internal.bindings.TargetBean.voidMethod() returns void");
319         }
320 
321         verify();
322     }
323 
324     @Test
325     public void void_method_as_terminal()
326     {
327         TargetBean bean = new TargetBean();
328         ComponentResources resources = mockComponentResources();
329         Location l = mockLocation();
330 
331         train_getComponent(resources, bean);
332 
333         replay();
334 
335         try
336         {
337             factory.newBinding("test binding", resources, null, "stringHolder.voidMethod()", l);
338             unreachable();
339         }
340         catch (RuntimeException ex)
341         {
342             assertMessageContains(ex,
343                     "Method org.apache.tapestry5.internal.bindings.StringHolder.voidMethod() returns void");
344         }
345 
346         verify();
347     }
348 
349     @Test
350     public void property_path_through_missing_property()
351     {
352         TargetBean bean = new TargetBean();
353         ComponentResources resources = mockComponentResources();
354         Location l = mockLocation();
355 
356         train_getComponent(resources, bean);
357 
358         replay();
359 
360         String propertyPath = "stringHolder.missingProperty.terminalProperty";
361 
362         try
363         {
364             factory.newBinding("test binding", resources, null, propertyPath, l);
365             unreachable();
366         }
367         catch (RuntimeException ex)
368         {
369             assertMessageContains(ex,
370                     "Class org.apache.tapestry5.internal.bindings.StringHolder does not contain a property",
371                     "\'missingProperty\'");
372         }
373 
374         verify();
375     }
376 
377     @Test
378     public void property_path_through_write_only_property()
379     {
380         TargetBean bean = new TargetBean();
381         ComponentResources resources = mockComponentResources();
382         Location l = mockLocation();
383 
384         train_getComponent(resources, bean);
385 
386         replay();
387 
388         String propertyPath = "writeOnly.terminalProperty";
389 
390         try
391         {
392             factory.newBinding("test binding", resources, null, propertyPath, l);
393             unreachable();
394         }
395         catch (RuntimeException ex)
396         {
397             assertMessageContains(ex,
398                     "Property \'writeOnly\' of class org.apache.tapestry5.internal.bindings.TargetBean",
399                     "is not readable (it has no read accessor method).");
400         }
401 
402         verify();
403     }
404 
405     @Test
406     public void primitive_property()
407     {
408         TargetBean bean = new TargetBean();
409         ComponentResources resources = newComponentResources(bean);
410         Location l = mockLocation();
411 
412         replay();
413 
414         Binding binding = factory.newBinding("test binding", resources, null, "intValue", l);
415 
416         assertSame(binding.getBindingType(), int.class);
417 
418         bean.setIntValue(1);
419 
420         assertEquals(binding.get(), 1);
421 
422         binding.set(2);
423 
424         assertEquals(bean.getIntValue(), 2);
425 
426         verify();
427     }
428 
429     @Test
430     public void read_only_property()
431     {
432         TargetBean bean = new TargetBean();
433         ComponentResources resources = newComponentResources(bean);
434         Location l = mockLocation();
435 
436         replay();
437 
438         Binding binding = factory.newBinding("test binding", resources, null, "readOnly", l);
439 
440         assertEquals(binding.get(), "ReadOnly");
441 
442         try
443         {
444             binding.set("fail");
445             unreachable();
446         }
447         catch (TapestryException ex)
448         {
449             assertEquals(ex.getMessage(),
450                     "Expression 'readOnly' for class org.apache.tapestry5.internal.bindings.TargetBean is read-only.");
451             assertEquals(ex.getLocation(), l);
452         }
453 
454         verify();
455     }
456 
457     @Test
458     public void write_only_property()
459     {
460         TargetBean bean = new TargetBean();
461         ComponentResources resources = newComponentResources(bean);
462         Location l = mockLocation();
463 
464         replay();
465 
466         Binding binding = factory.newBinding("test binding", resources, null, "writeOnly", l);
467 
468         binding.set("updated");
469 
470         assertEquals(bean.writeOnly, "updated");
471 
472         try
473         {
474             assertEquals(binding.get(), "ReadOnly");
475             unreachable();
476         }
477         catch (TapestryException ex)
478         {
479             assertEquals(ex.getMessage(),
480                     "Expression 'writeOnly' for class org.apache.tapestry5.internal.bindings.TargetBean is write-only.");
481             assertEquals(ex.getLocation(), l);
482         }
483 
484         verify();
485     }
486 
487     @Test
488     public void unknown_property()
489     {
490         TargetBean bean = new TargetBean();
491         ComponentResources resources = mockComponentResources();
492         Location l = mockLocation();
493 
494         train_getComponent(resources, bean);
495 
496         replay();
497 
498         try
499         {
500             factory.newBinding("test binding", resources, null, "missingProperty", l);
501             unreachable();
502         }
503         catch (RuntimeException ex)
504         {
505             assertMessageContains(ex,
506                     "Class org.apache.tapestry5.internal.bindings.TargetBean does not contain a property",
507                     "\'missingProperty\'");
508         }
509 
510         verify();
511     }
512 
513     @Test(dataProvider = "values")
514     public void special_prop_binding_values(String expression, Object expected)
515     {
516         Location l = mockLocation();
517         String description = "my description";
518         ComponentResources resources = mockComponentResources();
519         Component component = mockComponent();
520 
521         train_getComponent(resources, component);
522         train_getCompleteId(resources, "Does.not.matter");
523 
524         replay();
525 
526         Binding binding = factory.newBinding(description, resources, null, expression, l);
527 
528         assertEquals(binding.get(), expected);
529 
530         // All of these are invariatns, even though they are generated from the PropertyConduit.
531 
532         assertTrue(binding.isInvariant());
533 
534         verify();
535     }
536 
537     @DataProvider
538     public Object[][] values()
539     {
540         return new Object[][]
541         {
542         { "true", true, },
543         { "True", true, },
544         { " true ", true, },
545         { "false", false },
546         { "null", null },
547         { "3", 3l },
548         { " 37 ", 37l },
549         { " -227", -227l },
550         { " 5.", 5d },
551         { " -100.", -100d },
552         { " -0.0 ", -0d },
553         { "+50", 50l },
554         { "+7..+20", new IntegerRange(7, 20) },
555         { "+5.5", 5.5d },
556         { "1..10", new IntegerRange(1, 10) },
557         { " -20 .. -30 ", new IntegerRange(-20, -30) },
558         { "0.", 0d },
559         { " 227.75", 227.75d },
560         { " -10123.67", -10123.67d },
561         { "'Hello World'", "Hello World" },
562         { " 'Whitespace Ignored' ", "Whitespace Ignored" },
563         { " ' Inside ' ", " Inside " } };
564     }
565 }